__author__ = 'pstalidis'
import math
import hashlib
from collections import defaultdict, Counter
import networkx as nx
from networkx.algorithms import bipartite


def merge_vectors(vec1, vec2):
    result = Counter()
    for term in set(vec1.keys() + vec2.keys()):
        result[term] = float(vec1[term] + vec2[term]) / 2
    return result


def create_clusters(original, min_similarity=0.1):
    clusters = defaultdict(lambda: {"vector": Counter(), "items": []})
    for (vid, vector) in original:
        similarities = Counter()
        for cluster in clusters:
            similarities[cluster] = cosine_similarity(clusters[cluster]["vector"], vector)
        if len(similarities) != 0:
            (cid, similarity) = similarities.most_common(1)[0]
        else:
            (cid, similarity) = (0, 0)
        if similarity > min_similarity:
            clusters[cid]["items"].append(vid)
            clusters[cid]["vector"] = merge_vectors(clusters[cid]["vector"], vector)
        else:
            new_cid = hashlib.md5(vid).hexdigest()
            clusters[new_cid]["items"].append(vid)
            clusters[new_cid]["vector"] = vector
    return clusters


def cosine_similarity(vec1, vec2):
        intersection = set(vec1.keys()) & set(vec2.keys())
        numerator = sum([vec1[x] * vec2[x] for x in intersection])
        sum1 = sum([vec1[x]**2 for x in vec1.keys()])
        sum2 = sum([vec2[x]**2 for x in vec2.keys()])
        denominator = math.sqrt(sum1) * math.sqrt(sum2)
        if not denominator:
            return 0.0
        else:
            return float(numerator) / denominator


class ArtMAP():
    def __init__(self):
        self.Users = []
        self.Books = []
        self.Scores = defaultdict(dict)
        self.bigraph = nx.Graph()
        self.book_nodes = set()
        self.user_nodes = set()
        self.BookGraph = nx.Graph()

    def add_user(self, user):
        self.Users.append(user)

    def add_users(self, users):
        for user in users:
            self.add_user(user)

    def add_book(self, book):
        self.Books.append(book)

    def add_books(self, books):
        for book in books:
            self.add_book(book)

    def add_score(self, user, book, score):
        self.Scores[(user, book)] = score

    def add_scores(self, scores):
        for (user, book, score) in scores:
            self.add_score(user, book, {"score": score})

    def create_graph(self):
        self.bigraph.add_nodes_from(self.Users, bipartite=0)
        self.bigraph.add_nodes_from(self.Books, bipartite=1)
        # self.bigraph.add_edges_from(self.Scores.keys())
        temp = []
        for key in self.Scores:
            temp.append(key + (self.Scores[key],))
        self.bigraph.add_edges_from(temp)
        try:
            self.book_nodes, self.user_nodes = bipartite.sets(self.bigraph)
            self.BookGraph = bipartite.projected_graph(self.bigraph, self.book_nodes)
        except nx.exception.NetworkXError:
            pass  # network is not bipartite
        print self.BookGraph.nodes()

    def book_density(self):
        return round(bipartite.density(self.bigraph, self.book_nodes), 2)

    def user_density(self):
        return round(bipartite.density(self.bigraph, self.user_nodes), 2)

    def cluster_users(self):
        create_clusters(self.Users, 0.78)
